
// ===============================================================================================================
// -*- C++ -*-
//
// INIFile.cpp - INI File class, for program configuration.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#include <INIFile.hpp>

namespace CommLib {

INISection::INISection(const std::string & sectionName) : name(sectionName), lines()
{
	/* WARNING: This class is NOT optimized at all,
	it may also have some bugs, use at your own risk.
	Last modified: 30/11/11 - Guilherme */
}

bool INISection::GetInteger(const std::string & key, int & val) const
{
	std::list<const std::string>::const_iterator it = lines.begin();
	std::list<const std::string>::const_iterator end = lines.end();

	std::string fmt;
	bool result = false;

	while (it != end)
	{
		// Make format string for sscanf:
		fmt = key;
		fmt.append(" = %d");

		if (sscanf((*it).c_str(), fmt.c_str(), &val) == 1)
		{
			result = true;
			break;
		}

		++it;
	}

	return (result);
}

bool INISection::GetFloat(const std::string & key, float & val) const
{
	std::list<const std::string>::const_iterator it = lines.begin();
	std::list<const std::string>::const_iterator end = lines.end();

	std::string fmt;
	bool result = false;

	while (it != end)
	{
		// Make format string for sscanf:
		fmt = key;
		fmt.append(" = %f");

		if (sscanf((*it).c_str(), fmt.c_str(), &val) == 1)
		{
			result = true;
			break;
		}

		++it;
	}

	return (result);
}

bool INISection::GetString(const std::string & key, std::string & val) const
{
	std::list<const std::string>::const_iterator it = lines.begin();
	std::list<const std::string>::const_iterator end = lines.end();

	const char * p;
	bool result = false;

	while (it != end)
	{
		if (strncmp(key.c_str(), (*it).c_str(), key.length()) == 0)
		{
			p = (*it).c_str();

			while (*p++ != '"') // Skipp 1st '"'
				;

			while (*p != '"') // Continue until end of string
			{
				val.push_back(*p++);
			}

			// Done.
			result = true;
			break;
		}

		++it;
	}

	return (result);
}

bool INISection::GetVector(const std::string & key, MathLib::Vec3f & val) const
{
	std::list<const std::string>::const_iterator it = lines.begin();
	std::list<const std::string>::const_iterator end = lines.end();
	std::string fmt;

	while (it != end)
	{
		// Try to get a Vec3f:
		fmt = key;
		fmt.append(" = Vec3f(%f, %f, %f)");

		if (sscanf((*it).c_str(), fmt.c_str(), &val.x, &val.y, &val.z) == 3)
		{
			return (true);
		}

		++it;
	}

	return (false);
}

INISection::~INISection(void)
{
	// C++ will do the cleanup automatically.
}

// ===============================================================================================================
// Class INIFile:

INIFile::INIFile(const std::string & iniFile) : sections()
{
	Read(iniFile);
}

bool INIFile::Read(const std::string & iniFile)
{
	assert(!iniFile.empty());

	FILE * file = fopen(iniFile.c_str(), "rt");

	if (!file)
	{
		CommLib::DbgPrintf(PRINT_ERROR, "Unable to open config file \"%s\"", iniFile.c_str());
		return (false); // File cannot be opened.
	}

	// Split file into sections, skipping blank lines & comments:

	char line[1024];
	char sectionName[128];
	INISection * currSection = 0;

	while (fgets(line, sizeof(line), file))
	{
		if ((*line == ';') || (*line == '#') || (*line == '\n'))
		{
			// Comment line | blank, skip it.
			continue;
		}
		else if (*line == '[')
		{
			// New section declared
			int i = 0;

			while (line[i + 1] != ']')
			{
				sectionName[i] = line[i + 1]; // FIXME: Things will go wrong if section is empty -> []
				++i;
			}

			sectionName[i] = 0;

			currSection = new INISection(sectionName);
			sections.insert(SectionMap::value_type(sectionName, currSection));
		}
		else
		{
			// INI property line, send it to the current section

			if (currSection == 0)
			{
				// No sections defined yet, create a default 'Global' section:
				currSection = new INISection("Global");
				sections.insert(SectionMap::value_type("Global", currSection));
			}

			currSection->lines.push_back(line);
		}
	}

	fclose(file);
	CommLib::DbgPrintf(PRINT_MSG, "Parsed INI file \"%s\" successfully", iniFile.c_str());

	return (true);
}

const INISection * INIFile::GetSection(const std::string & sectionName, bool strict) const
{
	SectionMap::const_iterator it = sections.find(sectionName);

	if (it != sections.end())
	{
		return ((*it).second); // Found exact match, return it.
	}
	else if (!strict)
	{
		// Do a deeper seach, ignoring full match & case:
		it = sections.begin();
		SectionMap::const_iterator end = sections.end();

		while (it != end)
		{
			if (strstr((*it).first.c_str(), sectionName.c_str()) != 0) // Look for substring
			{
				return ((*it).second); // Got it!
			}
			else if (stricmp((*it).first.c_str(), sectionName.c_str()) == 0) // Try case-insensitive compare
			{
				return ((*it).second); // Got it!
			}
			else
			{
				++it; // Continue search...
			}
		}

		return (0); // No matching sections found.
	}
	else
	{
		return (0); // No matching sections found.
	}
}

void INIFile::Clear(void)
{
	if (!sections.empty())
	{
		SectionMap::const_iterator it = sections.begin();
		SectionMap::const_iterator end = sections.end();

		while (it != end)
		{
			delete (*it).second;
			++it;
		}

		sections.clear();
	}
}

INIFile::~INIFile(void)
{
	Clear(); // Free memory before exit.
}

}; // namespace CommLib {}